#include <crypto/aes.h>
#include <crypto/des.h>
#include <linux/crypto.h>
#include <crypto/skcipher.h>
#include <crypto/internal/skcipher.h>
#include "drv_cesa_if.h"
#include "hw_cesa_if.h"


void drv_cesa_ades_lock(uint32_t session);
void drv_cesa_ades_unlock(uint32_t session);
bool drv_cesa_check_keep(uint32_t session, uint32_t same_diff_both);
int fh_ce_cipher_init(struct crypto_tfm *tfm);
void fh_ce_cipher_exit(struct crypto_tfm *tfm);
int fh_ce_setkey(struct crypto_skcipher *tfm, const u8 *key, unsigned int keylen);
int fh_ce_skencrypt(struct skcipher_request *areq);
int fh_ce_skdecrypt(struct skcipher_request *areq);


static struct skcipher_alg algs[] = {
	{
		.base = {
			.cra_name = "cbc(aes)",
			.cra_driver_name = "cbc-aes-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= AES_MIN_KEY_SIZE,
		.max_keysize	= AES_MAX_KEY_SIZE,
		.ivsize		= AES_BLOCK_SIZE,
		.setkey		= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "ecb(aes)",
			.cra_driver_name = "ecb-aes-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= AES_MIN_KEY_SIZE,
		.max_keysize	= AES_MAX_KEY_SIZE,
		.ivsize		= 0,
		.setkey		= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "ctr(aes)",
			.cra_driver_name = "ctr-aes-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= AES_MIN_KEY_SIZE,
		.max_keysize	= AES_MAX_KEY_SIZE,
		.ivsize		= AES_BLOCK_SIZE,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "cfb(aes)",
			.cra_driver_name = "cfb-aes-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= AES_MIN_KEY_SIZE,
		.max_keysize	= AES_MAX_KEY_SIZE,
		.ivsize		= AES_BLOCK_SIZE,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "ofb(aes)",
			.cra_driver_name = "ofb-aes-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= AES_MIN_KEY_SIZE,
		.max_keysize	= AES_MAX_KEY_SIZE,
		.ivsize		= AES_BLOCK_SIZE,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "cbc(des)",
			.cra_driver_name = "cbc-des-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= DES_KEY_SIZE,
		.max_keysize	= DES_KEY_SIZE,
		.ivsize 	= DES_BLOCK_SIZE,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "ecb(des)",
			.cra_driver_name = "ecb-des-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= DES_KEY_SIZE,
		.max_keysize	= DES_KEY_SIZE,
		.ivsize 	= 0,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "cfb(des)",
			.cra_driver_name = "cfb-des-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= DES_KEY_SIZE,
		.max_keysize	= DES_KEY_SIZE,
		.ivsize 	= DES_BLOCK_SIZE,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "ofb(des)",
			.cra_driver_name = "ofb-des-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= DES_KEY_SIZE,
		.max_keysize	= DES_KEY_SIZE,
		.ivsize 	= DES_BLOCK_SIZE,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "cbc(des3_ede)",
			.cra_driver_name = "cbc-des3-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= DES3_EDE_KEY_SIZE,
		.max_keysize	= DES3_EDE_KEY_SIZE,
		.ivsize		= DES3_EDE_BLOCK_SIZE,
		.setkey		= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "ecb(des3_ede)",
			.cra_driver_name = "ecb-des3-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= DES3_EDE_KEY_SIZE,
		.max_keysize	= DES3_EDE_KEY_SIZE,
		.ivsize 	= 0,
		.setkey		= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "cfb(des3_ede)",
			.cra_driver_name = "cfb-des3-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= DES3_EDE_KEY_SIZE,
		.max_keysize	= DES3_EDE_KEY_SIZE,
		.ivsize 	= DES_BLOCK_SIZE,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
	{
		.base = {
			.cra_name = "ofb(des3_ede)",
			.cra_driver_name = "ofb-des3-fh-ce",
			.cra_priority = 400,
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_TYPE_SKCIPHER |
				CRYPTO_ALG_ASYNC |
				CRYPTO_ALG_KERN_DRIVER_ONLY,
			.cra_ctxsize = sizeof(HW_CESA_ADES_CTRL_S),
			.cra_module = THIS_MODULE,
			.cra_alignmask = 0xf,
			.cra_init = fh_ce_cipher_init,
			.cra_exit = fh_ce_cipher_exit,
		},
		.min_keysize	= DES3_EDE_KEY_SIZE,
		.max_keysize	= DES3_EDE_KEY_SIZE,
		.ivsize 	= DES_BLOCK_SIZE,
		.setkey 	= fh_ce_setkey,
		.encrypt	= fh_ce_skencrypt,
		.decrypt	= fh_ce_skdecrypt,
	},
};


int fh_ce_cipher_init(struct crypto_tfm *tfm)
{
	HW_CESA_ADES_CTRL_S *session = crypto_tfm_ctx(tfm);
	//struct crypto_skcipher *sktfm = __crypto_skcipher_cast(tfm);
	char alg_mode[9] = {0};
	char work_mode[9] = {0};
	int i;

	session->priv = g_cesa->p_priv;
	session->src_addr_attr = FH_CESA_ADES_ADDR_ATTR_VIR;
	session->dst_addr_attr = FH_CESA_ADES_ADDR_ATTR_VIR;

	sscanf(tfm->__crt_alg->cra_name, "%3s(%8s)", work_mode, alg_mode);
	for (i = 0; i<9; i++) {
		if (alg_mode[i] == 0x29)
			alg_mode[i] = 0;
	}

	if (strcmp(alg_mode, "des") == 0)
		session->algo_mode = HW_CESA_ADES_ALGO_MODE_DES;
	else if (strcmp(alg_mode, "des3_ede") == 0)
		session->algo_mode = HW_CESA_ADES_ALGO_MODE_TDES;
	else if (strcmp(alg_mode, "aes") == 0)
		session->algo_mode = HW_CESA_ADES_ALGO_MODE_AES128;
	else
		return CESA_ADES_ALGO_MODE_ERROR;

	if (strcmp(work_mode, "ecb") == 0)
		session->work_mode = HW_CESA_ADES_WORK_MODE_ECB;
	else if (strcmp(work_mode, "cbc") == 0)
		session->work_mode = HW_CESA_ADES_WORK_MODE_CBC;
	else if (strcmp(work_mode, "ctr") == 0) {
		if ((session->algo_mode == HW_CESA_ADES_ALGO_MODE_DES) ||
			(session->algo_mode == HW_CESA_ADES_ALGO_MODE_TDES))
			return CESA_ADES_WORK_ALGO_MODE_CONFLICT;

		session->work_mode = HW_CESA_ADES_WORK_MODE_CTR;
	} else if (strcmp(work_mode, "cfb") == 0)
		session->work_mode = HW_CESA_ADES_WORK_MODE_CFB;
	else if (strcmp(work_mode, "ofb") == 0)
		session->work_mode = HW_CESA_ADES_WORK_MODE_OFB;
	else
		return CESA_ADES_WORK_MODE_ERROR;

	if (hw_ades_ext_dma_req_chan(session))
		return CESA_ADES_HDLR_GET_FAILED;
		
	drv_cesa_ades_lock((u32)session);
	drv_cesa_check_keep((u32)session, 0);
	drv_cesa_ades_unlock((u32)session);

	return 0;
}

void fh_ce_cipher_exit(struct crypto_tfm *tfm)
{
	HW_CESA_ADES_CTRL_S *session = crypto_tfm_ctx(tfm);

	drv_cesa_check_keep((u32)session, 2);

	hw_ades_ext_dma_rel_chan(session);

	return;
}

int fh_ce_setkey(struct crypto_skcipher *tfm, const u8 *key,
			unsigned int keylen)
{
	HW_CESA_ADES_CTRL_S *session = crypto_skcipher_ctx(tfm);

	if (session->algo_mode == HW_CESA_ADES_ALGO_MODE_AES128 ||
		session->algo_mode == HW_CESA_ADES_ALGO_MODE_AES192 ||
		session->algo_mode == HW_CESA_ADES_ALGO_MODE_AES256) {
		switch (keylen) {
		case 16:
			session->algo_mode = HW_CESA_ADES_ALGO_MODE_AES128;
			break;
		case 24:
			session->algo_mode = HW_CESA_ADES_ALGO_MODE_AES192;
			break;
		case 32:
			session->algo_mode = HW_CESA_ADES_ALGO_MODE_AES256;
			break;
		default:
			return CESA_ADES_ALGO_MODE_ERROR;
		}
	}

	memcpy(session->key, key, keylen);
	session->self_key_gen = false;
	if (key[0] == 'f' && key[1] == 'u' && key[2] == 'l' && key[3] == 'l'
		&& key[4] == 'h' && key[5] == 'a' && key[6] == 'n') {
		session->efuse_para.mode &= ~(FH_CESA_ADES_KEY_SRC_USER | FH_CESA_ADES_KEY_SRC_EFUSE);
		session->efuse_para.mode |= FH_CESA_ADES_KEY_SRC_EFUSE;
	}

	drv_cesa_ades_lock((u32)session);
	drv_cesa_check_keep((u32)session, 0);
	drv_cesa_ades_unlock((u32)session);

	return 0;
}

int32_t fh_ce_process_ext(uint32_t session, struct scatterlist *src, struct scatterlist *dst, uint32_t length)
{
	HW_CESA_ADES_CTRL_S *ades_ce = (HW_CESA_ADES_CTRL_S *)session;
	uint32_t phy_src_addr = 0;
	uint32_t phy_dst_addr = 0;
	uint32_t maxblock_cnt = length / ADES_MAX_BLOCK;
	uint32_t maxblock_rem = length % ADES_MAX_BLOCK;
	uint32_t i, proc_len, total_len = 0;
	int32_t ret;

	if (session == 0)
		return CESA_ADES_HDLR_SET_INVALID;

	if (ades_ce->src_addr_attr != FH_CESA_ADES_ADDR_ATTR_PHY)
		phy_src_addr = drv_cesa_ades_phy_data_addr(session);

	if (ades_ce->dst_addr_attr != FH_CESA_ADES_ADDR_ATTR_PHY)
		phy_dst_addr = drv_cesa_ades_phy_data_addr(session);

	for (i = 0; i < maxblock_cnt + (maxblock_rem ? 1 : 0); i++) {

		proc_len = (i == maxblock_cnt) ? maxblock_rem : ADES_MAX_BLOCK;

		if (phy_src_addr) {
			ret = drv_cesa_ades_dma_sync_single_for_device_sg(session, src, proc_len, total_len);
			if (ret)
				break;
		}

		ret = drv_cesa_ades_process(session, phy_src_addr, phy_dst_addr, proc_len);
		if (ret)
			break;

		if (phy_dst_addr) {
			ret = drv_cesa_ades_dma_sync_single_for_cpu_sg(session, dst, proc_len, total_len);
			if (ret)
				break;
		}

		total_len += proc_len;
	}

	return ret;
}

int fh_ce_skencrypt(struct skcipher_request *areq)
{
	struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(areq);
	HW_CESA_ADES_CTRL_S *session = crypto_skcipher_ctx(tfm);

	if (session->oper_mode != HW_CESA_ADES_OPER_MODE_ENC) {
		session->oper_mode = HW_CESA_ADES_OPER_MODE_ENC;
		//hw_cesa_ades_restore_lastiv(session);
		drv_cesa_ades_lock((u32)session);
		drv_cesa_check_keep((u32)session, 0);
		drv_cesa_ades_unlock((u32)session);
	}

	memcpy(session->iv_init, areq->iv, tfm->ivsize);

	return fh_ce_process_ext((u32)session, areq->src, areq->dst, areq->cryptlen);
}

int fh_ce_skdecrypt(struct skcipher_request *areq)
{
	struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(areq);
	HW_CESA_ADES_CTRL_S *session = crypto_skcipher_ctx(tfm);

	if (session->oper_mode != HW_CESA_ADES_OPER_MODE_DEC) {
		session->oper_mode = HW_CESA_ADES_OPER_MODE_DEC;
		//hw_cesa_ades_restore_lastiv(session);
		drv_cesa_ades_lock((u32)session);
		drv_cesa_check_keep((u32)session, 0);
		drv_cesa_ades_unlock((u32)session);
	}

	memcpy(session->iv_init, areq->iv, tfm->ivsize);

	return fh_ce_process_ext((u32)session, areq->src, areq->dst, areq->cryptlen);
}

int fh_ce_register_algs(struct device *dev)
{
	int err;
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(algs); i++) {
		err = crypto_register_skcipher(&algs[i]);
		if (err) {
			dev_err(dev, "ERROR: Fail to register %s\n",
				algs[i].base.cra_name);
			return err;
		}
	}
	return 0;
}

void fh_ce_unregister_algs(struct device *dev)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(algs); i++) {
		dev_info(dev, "Unregister %d %s\n", i,
			 algs[i].base.cra_name);
		crypto_unregister_skcipher(&algs[i]);
	}
}

